package com.huawei.lts.common.utils;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.time.FastDateFormat;
import org.apache.commons.lang3.time.FastTimeZone;

import java.sql.Timestamp;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 功能描述 日期/时间类工具函数
 *
 * @author jWX1116205
 * @since 2022-01-19
 */
@Slf4j
public class DateUtils {
    interface SdConverter {
        /**
         * 将字符串转换成日期
         *
         * @param date 输入字符串
         * @return Date对象
         */
        Date convert(String date);
    }

    /**
     * 将日期转换成字符串
     *
     * @author q00197955
     * @version [版本号, 2019-06-26]
     */
    interface DsConverter {
        /**
         * 将日期转换成字符串
         *
         * @param date Date对象
         * @return 转换后的字符串
         */
        String convert(Date date);
    }

    /**
     * 一小时内分钟数
     */
    public static final int MINUTES_IN_HOUR = 60;

    /**
     * 标准19位格式
     */
    public final static String STANDARD_19 = "yyyy-MM-dd HH:mm:ss";

    /**
     * 标准23位格式
     */
    public final static String STANDARD_23 = "yyyy-MM-dd HH:mm:ss.SSS";

    /**
     * 标准14位格式
     */
    public final static String STANDARD_14 = "yyyyMMddHHmmss";

    /**
     * 标准12位格式
     */
    public final static String STANDARD_12 = "yyyyMMddHHmm";

    /**
     * 标准17位格式
     */
    public final static String STANDARD_17 = "yyyyMMddHHmmssSSS";

    /**
     * 标准8位格式
     */
    public final static String STANDARD_8 = "yyyyMMdd";

    /**
     * 标准10位格式
     */
    public final static String STANDARD_10 = "yyyyMMddHH";

    /**
     * 标准10位格式
     */
    public final static String STANDARD_10_SPLIT = "yyyy-MM-dd";

    /**
     * 秒与毫秒的转换单位
     */
    private static final int MILLISECOND = 1000;

    /**
     * UTC标识
     */
    private final static String UTC = "UTC";

    /**
     * 夏令时标识
     */
    private final static String DST = "DST";

    /**
     * UTC时区
     */
    private final static TimeZone UTC_TIMEZONE = TimeZone.getTimeZone("UTC");

    private final static Map<String, DsConverter> DS_MAPPING = new ConcurrentHashMap<>();

    private final static Map<String, DsConverter> STZ_MAPPING = new ConcurrentHashMap<>();

    private final static Map<String, SdConverter> SD_MAPPING = new ConcurrentHashMap<>();

    private final static Map<String, SimpleDateFormat> UTC_FORMATER = new ConcurrentHashMap<>();

    private final static CalendarGenerator CALENDAR_GENERATOR = new CalendarGenerator();

    static {
        DS_MAPPING.put(STANDARD_14, new StandardDsConverter(14));
        DS_MAPPING.put(STANDARD_12, new StandardDsConverter(12));
        DS_MAPPING.put("yyyyMMddHH", new StandardDsConverter(10));
        DS_MAPPING.put(STANDARD_8, new StandardDsConverter(8));
        DS_MAPPING.put("yyyyMM", new StandardDsConverter(6));

        STZ_MAPPING.put(STANDARD_14, new StandardDsSTZConverter(14));
        STZ_MAPPING.put(STANDARD_12, new StandardDsSTZConverter(12));
        STZ_MAPPING.put("yyyyMMddHH", new StandardDsSTZConverter(10));
        STZ_MAPPING.put(STANDARD_8, new StandardDsSTZConverter(8));
        STZ_MAPPING.put("yyyyMM", new StandardDsSTZConverter(6));

        SD_MAPPING.put(STANDARD_14, new StandardSdConverter(14));
        SD_MAPPING.put(STANDARD_12, new StandardSdConverter(12));
        SD_MAPPING.put("yyyyMMddHH", new StandardSdConverter(10));
        SD_MAPPING.put(STANDARD_8, new StandardSdConverter(8));
        SD_MAPPING.put("yyyyMM", new StandardSdConverter(6));
    }

    /**
     * 获取UTC时间
     *
     * @param format 格式
     * @return UTC时间
     */
    public static String getUtcTime(String format) {
        Date date = getUTCTime();
        return formatDate(date, format);
    }

    /**
     * 获取UTC时间
     *
     * @return Date
     */
    public static Date getUTCTime() {
        // 1、取得本地时间：
        Calendar cal = Calendar.getInstance();
        // 2、取得时间偏移量：
        int zoneOffset = cal.get(Calendar.ZONE_OFFSET);
        // 3、取得夏令时差：
        int dstOffset = cal.get(Calendar.DST_OFFSET);
        // 4、从本地时间里扣除这些差量，即可以取得UTC时间：
        cal.add(Calendar.MILLISECOND, -(zoneOffset + dstOffset));
        Date date = cal.getTime();
        return date;
    }

    /**
     * 将本地时间转换成UTC时间
     *
     * @param date 本地时间
     * @param zone 本地时区
     * @return Date
     */
    public final static Date getUtcTime(Date date, TimeZone zone) {
        if (null == date) {
            return null;
        }

        String dateStr = formatDate(date);
        String utcStr = getUtcTime(dateStr, zone);
        return parseDate(utcStr);
    }

    /**
     * 转换字符串日期为日期
     *
     * @param dateStr 时间字符串
     * @param format  格式
     * @return 解析后的Date
     */
    public final static Date parseDate(String dateStr, String format) {
        return StringUtils.isBlank(dateStr) ? null : getSdConverter(format).convert(dateStr);
    }

    /**
     * 用标准14位格式转换日期
     *
     * @param dateStr 时间字符串
     * @return 转换后的Date
     */
    public final static Date parseDate(String dateStr) {
        return parseDate(dateStr, STANDARD_14);
    }

    /**
     * 将标准14位格式的时间转换为时间戳
     *
     * @param dateStr 时间字符串
     * @return 转换后的Timestamp
     */
    public final static Timestamp parse(String dateStr) {
        if (StringUtils.isBlank(dateStr)) {
            return null;
        }

        FastDateFormat dateFormat = FastDateFormat.getInstance(STANDARD_14, FastTimeZone.getGmtTimeZone());
        try {
            return new Timestamp(dateFormat.parse(dateStr).getTime());
        } catch (ParseException e) {
            log.error(StringUtils.EMPTY, e);
        }

        return null;
    }

    /**
     * 格式化Date
     *
     * @param date   目标Date
     * @param format 格式
     * @return 格式化后的字符串
     */
    public final static String formatDate(Date date, String format) {
        if (null == date) {
            date = new Date();
        }
        return getDsConverter(format).convert(date);
    }

    /**
     * 用标准14位格式格式化日期
     *
     * @param date 要格式的Date对象
     * @return 格式化后的字符串
     */
    public final static String formatDate(Date date) {
        return formatDate(date, STANDARD_14);
    }

    /**
     * 用标准14位格式格式化日期
     *
     * @param timestamp 要格式的Timestamp对象
     * @return 格式化后的日期
     */
    public static String formatUTC(Timestamp timestamp) {
        if (null == timestamp) {
            return null;
        }
        return DateFormatUtils.formatUTC(new Date(timestamp.getTime()), STANDARD_14);
    }

    /**
     * 获取当前时间
     *
     * @param format 格式
     * @return 格式化后的字符串
     */
    public final static String getCurrentTime(String format) {
        return formatDate(new Date(), format);
    }

    /**
     * 获取当前时间
     *
     * @return 格式化后的字符串
     */
    public final static String getCurrentTime() {
        return formatDate(new Date());
    }

    /**
     * 获取当前时间
     *
     * @return Date
     */
    public final static Date getCurrentDate() {
        return new Date();
    }

    /**
     * 设置偏移天数, 基于UTC时区
     *
     * @param date 基于utc的date
     * @param days 偏移天数
     * @return Date 偏移之后的日期，基于utc的date
     */
    public static Date adjustDay(Date date, int days) {
        return adjustDay(date, days, null);
    }

    /**
     * 设置偏移天数，基于指定时区
     *
     * @param date 基于utc的date
     * @param days 偏移的天数
     * @param zone 时区
     * @return Date 偏移之后的日期，基于utc的date
     */
    public static Date adjustDay(Date date, int days, TimeZone zone) {
        date = (Date) date.clone();
        Calendar calendar = Calendar.getInstance();
        if (null != zone) {
            calendar.setTimeZone(zone);
        }
        calendar.setTime(date);
        calendar.add(Calendar.DAY_OF_MONTH, days);
        date.setTime(calendar.getTimeInMillis());

        return date;
    }

    /**
     * 将本地时间转换成UTC时间
     *
     * @param format   格式
     * @param dateStr  本地日期
     * @param timeZone 本地时区
     * @return 转换后的字符串
     */
    public final static String getUtcTime(String format, String dateStr, TimeZone timeZone) {
        SimpleDateFormat formater = getDateFormat(format);
        formater.setTimeZone(timeZone);
        try {
            Date date = formater.parse(dateStr);
            formater.setTimeZone(UTC_TIMEZONE);
            return formater.format(date);
        } catch (Exception e) {
            return dateStr;
        }
    }

    /**
     * 以标准14位格式将本地时间转换成UTC时间
     *
     * @param dateStr 本地时间
     * @param zone    本地时区
     * @return 转换后的字符串
     */
    public final static String getUtcTime(String dateStr, TimeZone zone) {
        return getUtcTime(STANDARD_14, dateStr, zone);
    }

    /**
     * 获取转换器
     *
     * @param format 格式
     * @return the SimpleDateFormat
     */
    private static SimpleDateFormat getDateFormat(String format) {
        SimpleDateFormat formater = UTC_FORMATER.get(format);
        if (null == formater) {
            formater = new SimpleDateFormat(format);
            UTC_FORMATER.put(format, formater);
        }

        return (SimpleDateFormat) formater.clone();
    }

    /**
     * 获取转换器
     *
     * @param format 格式
     * @return DsConverter
     */
    private static DsConverter getDsConverter(String format) {
        DsConverter converter = DS_MAPPING.get(format);
        if (null == converter) {
            converter = new GenericDsConverter(format);
            DS_MAPPING.put(format, converter);
        }

        return converter;
    }

    /**
     * 获取转换器
     *
     * @param format 格式
     * @return SdConverter
     */
    private static SdConverter getSdConverter(String format) {
        SdConverter converter = SD_MAPPING.get(format);
        if (null == converter) {
            converter = new GenericSdConverter(format);
            SD_MAPPING.put(format, converter);
        }

        return converter;
    }

    /**
     * 时间比较 将两个标准14位时间字符串进行比较
     *
     * @param strDateTime1 时间字符串1
     * @param strDateTime2 时间字符串2
     * @return 返回strDateTime1比strDateTime2的滞后秒数
     * @throws IllegalArgumentException
     */
    public static long compareTime(String strDateTime1, String strDateTime2) throws IllegalArgumentException {
        Date date1 = parseDate(strDateTime1);
        if (null == date1) {
            throw new IllegalArgumentException(
                    "Paragram: strDateTime1[" + strDateTime1 + "] is invalid, can not be converted to Date!");
        }
        Date date2 = parseDate(strDateTime2);
        if (null == date2) {
            throw new IllegalArgumentException(
                    "Paragram: strDateTime2[" + strDateTime2 + "] is invalid, can not be converted to Date!");
        }
        return (date1.getTime() - date2.getTime()) / MILLISECOND;
    }

    /**
     * 默认通用DsConverter
     *
     * @author q00197955
     * @version [版本号, 2019-06-26]
     */
    private static class GenericDsConverter implements DsConverter {
        private FastDateFormat dateFormat;

        GenericDsConverter(String format) {
            dateFormat = FastDateFormat.getInstance(format);

        }

        @Override
        public String convert(Date date) {
            return dateFormat.format(date);
        }
    }

    /**
     * 默认通用SdConverter
     *
     * @author q00197955
     * @version [版本号, 2019-06-26]
     */
    private static class GenericSdConverter implements SdConverter {
        private SimpleDateFormat dateFormat;

        GenericSdConverter(String format) {
            dateFormat = new SimpleDateFormat(format);
        }

        @Override
        public Date convert(String date) {
            try {
                SimpleDateFormat format = (SimpleDateFormat) dateFormat.clone();
                return format.parse(date);
            } catch (ParseException e) {
                return null;
            }
        }
    }

    static class StandardDsSTZConverter implements DsConverter {
        private TimeZone timeZone;

        private int length;

        StandardDsSTZConverter(int length) {
            this.length = length;
            timeZone = getSystemTimeZone();
        }

        private TimeZone getSystemTimeZone() {
            String zoneId = "GMT";
            return TimeZone.getTimeZone(zoneId);
        }

        @Override
        public String convert(Date date) {
            GregorianCalendar gregoCalendar = CALENDAR_GENERATOR.getCalendar();
            gregoCalendar.setTimeZone(timeZone);
            gregoCalendar.setTime(date);

            int iYear = gregoCalendar.get(Calendar.YEAR);
            int iMonth = gregoCalendar.get(Calendar.MONTH) + 1;
            int iDay = gregoCalendar.get(Calendar.DAY_OF_MONTH);
            int iHour = gregoCalendar.get(Calendar.HOUR_OF_DAY);
            int iMinute = gregoCalendar.get(Calendar.MINUTE);
            int iSecond = gregoCalendar.get(Calendar.SECOND);
            StringBuilder sb = new StringBuilder(14);
            sb.append(iYear);
            if (iMonth < 10) {
                sb.append('0');
            }
            sb.append(iMonth);
            if (iDay < 10) {
                sb.append('0');
            }
            sb.append(iDay);
            if (iHour < 10) {
                sb.append('0');
            }
            sb.append(iHour);
            if (iMinute < 10) {
                sb.append('0');
            }
            sb.append(iMinute);
            if (iSecond < 10) {
                sb.append('0');
            }
            sb.append(iSecond);

            sb.setLength(length);

            return sb.toString();
        }
    }

    /**
     * 标准6/8/10/12/14位格式化
     *
     * @author q00197955
     * @version [版本号, 2019-06-26]
     */
    static class StandardDsConverter implements DsConverter {
        private TimeZone timeZone;

        private int length;

        StandardDsConverter(int length) {
            this.length = length;
            timeZone = TimeZone.getDefault();
        }

        @Override
        public String convert(Date date) {
            GregorianCalendar gca = CALENDAR_GENERATOR.getCalendar();
            gca.setTimeZone(timeZone);
            gca.setTime(date);

            int iYear = gca.get(Calendar.YEAR);
            int iMonth = gca.get(Calendar.MONTH) + 1;
            int iDay = gca.get(Calendar.DAY_OF_MONTH);
            int iHour = gca.get(Calendar.HOUR_OF_DAY);
            int iMinute = gca.get(Calendar.MINUTE);
            int iSecond = gca.get(Calendar.SECOND);
            StringBuilder stringBuilder = new StringBuilder(14);
            stringBuilder.append(iYear);
            if (iMonth < 10) {
                stringBuilder.append('0');
            }
            stringBuilder.append(iMonth);
            if (iDay < 10) {
                stringBuilder.append('0');
            }
            stringBuilder.append(iDay);
            if (iHour < 10) {
                stringBuilder.append('0');
            }
            stringBuilder.append(iHour);
            if (iMinute < 10) {
                stringBuilder.append('0');
            }
            stringBuilder.append(iMinute);
            if (iSecond < 10) {
                stringBuilder.append('0');
            }
            stringBuilder.append(iSecond);

            stringBuilder.setLength(length);

            return stringBuilder.toString();
        }
    }

    /**
     * 标准6/8/10/12/14位格式化
     *
     * @author q00197955
     * @version [版本号, 2019-06-26]
     */
    static class StandardSdConverter implements SdConverter {
        private TimeZone timeZone;

        private int length;

        StandardSdConverter(int length) {
            this.length = length;
            timeZone = TimeZone.getDefault();
        }

        @Override
        public Date convert(String date) {
            if (null == date || length != date.length()) {
                return null;
            }
            try {
                char[] charArr = date.toCharArray();
                int iYear =
                        (charArr[0] - 48) * 1000 + (charArr[1] - 48) * 100 + (charArr[2] - 48) * 10 + (charArr[3] - 48);
                int iMonth = (charArr[4] - 48) * 10 + (charArr[5] - 48);
                int iDay = (length > 6) ? (charArr[6] - 48) * 10 + (charArr[7] - 48) : 1;
                int iHour = (length > 8) ? (charArr[8] - 48) * 10 + (charArr[9] - 48) : 0;
                int iMinute = (length > 10) ? (charArr[10] - 48) * 10 + (charArr[11] - 48) : 0;
                int iSecond = (length > 12) ? (charArr[12] - 48) * 10 + (charArr[13] - 48) : 0;

                boolean validate = (iYear > 9999) || (iYear < 1000) || (iMonth > 12) || (iMonth < 1) || (iDay > 31)
                        || (iDay < 1) || (iHour > 24) || (iHour < 0) || (iMinute > 59) || (iMinute < 0) || (iSecond > 59)
                        || (iSecond < 0);

                if (validate) {
                    return null;
                }

                GregorianCalendar gca = CALENDAR_GENERATOR.getCalendar();
                gca.setTimeZone(timeZone);
                gca.set(iYear, iMonth - 1, iDay, iHour, iMinute, iSecond);
                gca.set(Calendar.MILLISECOND, 0);
                return gca.getTime();
            } catch (NumberFormatException e) {
                return null;
            }
        }

    }

    private static class CalendarGenerator {
        private GregorianCalendar gca;

        CalendarGenerator() {
            gca = new GregorianCalendar();
        }

        GregorianCalendar getCalendar() {
            return (GregorianCalendar) gca.clone();
        }
    }
}